﻿using Wlmy.Ids4.Common;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Text;
using System.Threading.Tasks;

namespace Wlmy.Ids4.Repository
{
    public class BaseRepository<T,T2>:IBaseRepository<T> where T2:DbContext where T:class
    {
        protected readonly IUnitOfWork<T2> _unitOfWork  = StaticInfo.GetService<IUnitOfWork<T2>>();
        protected T2 _Db => _unitOfWork.GetDbContext();
        public async Task AddAsync(T entity)
        {
            await _Db.Set<T>().AddAsync(entity);
        }

        public async Task AddRangeAsync(List<T> entities)
        {
            await _Db.Set<T>().AddRangeAsync(entities);
        }

        public async Task UpdateAsync(T entity)
        {
            await Task.FromResult(_Db.Set<T>().Update(entity));
        }

        public void UpdateRange(List<T> entities)
        {
            _Db.Set<T>().UpdateRange(entities);
        }


        public async Task DeleteAsync(T entity)
        {
            await Task.FromResult(_Db.Set<T>().Remove(entity));
        }
        public async Task DeleteAsync(int id)
        {
            var entity = await GetByIdAsync(id);
            if (entity != null)
            {
                await DeleteAsync(entity);
                return;
            }
        }
        public void DeleteRange(List<T> entities)
        {
            _Db.Set<T>().RemoveRange(entities);
        }

        public async Task<T> SingleAsync(Expression<Func<T, bool>> expression)
        {
            return await IncludeNavProperty().SingleOrDefaultAsync(expression);
        }
        public async Task<T> SingleIncludeNavAsync(Expression<Func<T, bool>> expression)
        {
            return await IncludeNavProperty().SingleOrDefaultAsync(expression);
        }
        private IQueryable<T> IncludeNavProperty()
        {
            var query = _Db.Set<T>().AsQueryable();
            foreach (var nav in _Db.Model.FindEntityType(typeof(T)).GetNavigations())
            {
                query = query.Include(CreateNav(nav));
            }
            return query;
        }
        public Expression<Func<T, object>> CreateNav(INavigation nav)
        {
            var lambdaParam = Expression.Parameter(typeof(T));

            var body = Expression.PropertyOrField(lambdaParam, nav.Name);
            //body = IncludeChildNav(nav, body, typeof(T));
            return Expression.Lambda<Func<T, object>>(body, lambdaParam);
        }
        public IQueryable<T> GetALL()
        {
            return _Db.Set<T>().AsQueryable();
        }

        public IQueryable<T> Where(Expression<Func<T, bool>> expression)
        {
            return _Db.Set<T>().Where(expression);
        }

        public async Task<bool> AnyAsync(Expression<Func<T, bool>> expression)
        {
            return await _Db.Set<T>().AnyAsync(expression);
        }

        public async Task<T> GetByIdAsync(int id)
        {
            return await _Db.Set<T>().FindAsync(id);
        }
        public async Task<T> GetByIdIncludeNavAsync(int id)
        {
            return await IncludeNavProperty().SingleOrDefaultAsync(CreateEqualityExpressionForId(id));
        }
        public async Task SaveChangesAsync()
        {
            await _unitOfWork.SaveChangesAsync();
        }
        private Expression<Func<T, bool>> CreateEqualityExpressionForId(int id)
        {
            var lambdaParam = Expression.Parameter(typeof(T));

            var leftExpression = Expression.PropertyOrField(lambdaParam, "Id");

            var idValue = Convert.ChangeType(id, typeof(int));

            Expression<Func<object>> closure = () => idValue;
            var rightExpression = Expression.Convert(closure.Body, leftExpression.Type);

            var lambdaBody = Expression.Equal(leftExpression, rightExpression);

            return Expression.Lambda<Func<T, bool>>(lambdaBody, lambdaParam);
        }

        public void SaveChanges()
        {
            _unitOfWork.SaveChanges();
        }
    }
}
